using System;
using System.Collections.Generic;

public sealed class Program {
   public static void Main() {
      // Construct a MailManager object
      MailManager mm = new MailManager();

      // Construct a Fax object
      Fax f = new Fax(mm);

      // Since no GCs have occurred, the Fax object 
      // will receive the event notification
      mm.SimulateNewMail();

      // Force a GC for testing
      GC.Collect();

      // Since a GC has occurred, the Fax object 
      // will not receive the event notification
      mm.SimulateNewMail();
   }
}

internal sealed class MailManager {
   private struct WeakDelegate<TEventArgs> where TEventArgs : EventArgs {
      private EventHandler<TEventArgs> m_staticCallback;
      private WeakReference m_object;
      private System.Reflection.MethodInfo m_method;
      private WeakReference m_weakDelegate;

      public WeakDelegate(EventHandler<TEventArgs> callback) {
         if (callback.Target == null) {
            // Initialize all fields for a static callback
            m_staticCallback = callback;
            m_weakDelegate = null;
            m_object = null;
            m_method = null;
         } else {
            // Initialize all fields for an instance callback
            m_staticCallback = null;

            // For an instance callback, save a WeakReference to the delegate
            // To improve performance, we use this if it hasn't been GC'd
            m_weakDelegate = new WeakReference(callback);

            // For instance callback, save WeakReference to target and Method
            m_object = new WeakReference(callback.Target);
            m_method = callback.Method;
         }
      }

      public Boolean Matches(EventHandler<TEventArgs> callback) {
         if (callback == null) throw new ArgumentNullException("callback");

         // If static method and match, return true
         if (m_staticCallback == callback) return true;

         // Return true if targets and methods match
         return (m_object.Target == callback.Target) && (m_method == callback.Method);
      }

      public Boolean WasTargetCollected() {
         // Returns false for static methods where there is no target
         if (m_staticCallback != null) return false;

         return m_object.Target == null;
      }

      public void Invoke(Object sender, TEventArgs e) {
         // For a static callback, just call it
         EventHandler<TEventArgs> callback = m_staticCallback;
         if (callback == null) {
            // For an instance method, call the delegate if not GC'd
            callback = m_weakDelegate.Target as EventHandler<TEventArgs>;
            if (callback == null) {
               // Delegate was GC'd, try to recreate it
               Object target = m_object.Target;
               if (target == null) return;   // Don't invoke if target GC'd
               callback = (EventHandler<TEventArgs>)Delegate.CreateDelegate(typeof(EventHandler<TEventArgs>), target, m_method);
               m_weakDelegate = new WeakReference(callback);
            }
         }
         callback(sender, e);
      }
   }

   // The collection maintains the set of WeakReferences to delegates
   private List<WeakDelegate<EventArgs>> m_NewMailCallbacks = new List<WeakDelegate<EventArgs>>();

   // This event is raised when new e-mail arrives
   public event EventHandler<EventArgs> NewMail {
      add {
         lock (m_NewMailCallbacks) {
            // Construct a WeakReference around the passed-in delegate
            // and add the WeakReference to our collection
            m_NewMailCallbacks.Add(new WeakDelegate<EventArgs>(value));
         }
      }

      remove {
         lock (m_NewMailCallbacks) {
            // Ensures that we only remove one delegate at a time
            Boolean alreadyRemovedOne = false;

            // Scan our collection looking for a matching delegate and remove it
            for (Int32 n = 0; n < m_NewMailCallbacks.Count; n++) {

               // Compare the passed delegate with the saved ObjectAndMethod instance
               // If a match, remove it
               if (!alreadyRemovedOne && m_NewMailCallbacks[n].Matches(value)) {
                  alreadyRemovedOne = true;
                  m_NewMailCallbacks.RemoveAt(n);
                  n--;        // Go back an entry
                  continue;   // Try next entry
               }

               // While scanning, remove any entries that refer to collected targets
               if (m_NewMailCallbacks[n].WasTargetCollected()) {
                  m_NewMailCallbacks.RemoveAt(n);
                  n--;        // Go back an entry
                  continue;   // Try next entry
               }
            }
         }
      }
   }

   // Call this method to simulate new mail arriving 
   public void SimulateNewMail() {
      Console.WriteLine("About to raise the NewMail event");
      OnNewMail(EventArgs.Empty);
   }

   // This method raises the NewMail event
   private void OnNewMail(EventArgs e) {
      lock (m_NewMailCallbacks) {

         // Scan our collection, calling back each delegate
         for (Int32 n = 0; n < m_NewMailCallbacks.Count; n++) {

            // While scanning, remove any entries that refer to collected targets
            if (m_NewMailCallbacks[n].WasTargetCollected()) {
               m_NewMailCallbacks.RemoveAt(n);
               n--;        // Go back an entry
               continue;   // Try next entry
            }

            // Invoke the callback for this ObjectAndMethod
            m_NewMailCallbacks[n].Invoke(this, e);
         }
      }
   }
}


internal sealed class Fax {
   // When constructed, register interest in the MailManager's NewMail event
   public Fax(MailManager mm) {
      mm.NewMail += StaticGotMail;
      mm.NewMail += GotMail;
   }

   // The method is called when NewMail arrives
   public void GotMail(Object sender, EventArgs e) {
      // Just prove that we got here
      Console.WriteLine("In Fax.GotMail");
   }

   public static void StaticGotMail(Object sender, EventArgs e) {
      // Just prove that we got here
      Console.WriteLine("In static Fax.GotMail");
   }
}

